package token

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"time"

	"go-papers/cache"
	"go-papers/common/go-wechat/common"
	"go-papers/pkg/lognew"
)

type CTokenJson struct {
	AccessToken string `json:"access_token"`
	ExpiresIn   int    `json:"expires_in"`
	ErrCode     int    `json:"errcode"`
	ErrMsg      string `json:"errmsg"`
}

type CTicketJson struct {
	Ticket    string `json:"ticket"`
	ExpiresIn int    `json:"expires_in"`
	ErrCode   int    `json:"errcode"`
	ErrMsg    string `json:"errmsg"`
}

//https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi

type CToken struct {
	m_tokenJson          CTokenJson
	m_ticketJson         CTicketJson
	m_userInfo           common.CUserInfo
	m_isVaild            bool
	m_isVaildTicket      bool
	m_error              int
	m_updateTokenChannel chan bool
}

func (this *CToken) GetUserInfo() *common.CUserInfo {
	return &this.m_userInfo
}

func (this *CToken) GetToken(timeoutMS int64) (token []byte, e error) {
	var isTimeout bool = false
	if this.m_isVaild == false {
		this.m_updateTokenChannel <- true
		if timeoutMS <= 0 {
			return nil, errors.New("token is vaild")
		} else {
			t := time.After(time.Duration(timeoutMS) * time.Millisecond)
		end:
			for {
				if this.m_isVaild == true {
					isTimeout = false
					break end
				}
				select {
				case <-t:
					isTimeout = true
					break end
				default:
				}
				time.Sleep(100 * time.Millisecond)
			}
		}
	}
	if isTimeout == true {
		return nil, errors.New("timeout")
	}
	return []byte(this.m_tokenJson.AccessToken), nil
}

func (this *CToken) UpdateToken(timeoutMS int64) (token []byte, e error) {
	this.m_updateTokenChannel <- true
	this.m_isVaild = false
	return this.GetToken(timeoutMS)
}

func (this *CToken) init(info *common.CUserInfo) {
	this.m_userInfo = *info
	if this.m_userInfo.AppId == "" || this.m_userInfo.AppSecret == "" {
		log.Println("error wechat appId set")
		return
	}

	this.m_updateTokenChannel = make(chan bool, 1)
	this.m_isVaild = false
	go func() {
		for {
			err := this.sendTokenRequest(info)
			if err != nil {
				lognew.Error(context.TODO(), "sendTokenRequestError", lognew.Fields{"err": err.Error(), "info": info})
			}
			if this.m_tokenJson.AccessToken == "" {
				this.m_error++
				if this.m_error >= 3 {
					time.Sleep(1200 * 1000 * time.Millisecond)
					this.m_error = 1
				} else {
					time.Sleep(60 * 1000 * time.Millisecond)
				}
				continue
			}
			select {
			case <-this.m_updateTokenChannel:
				fmt.Println("[INFO] update token request")
			case <-time.After(time.Duration(7202) * time.Second):
				this.m_isVaild = false
				lognew.Info(context.TODO(), "tokenTimeout", lognew.Fields{"token": this.m_tokenJson})
			}
		}
	}()
}

func (this *CToken) InitToken(info *common.CUserInfo) error {
	this.m_userInfo = *info
	if this.m_userInfo.AppId == "" || this.m_userInfo.AppSecret == "" {
		log.Println("error wechat appId set")
		return nil
	}
	err := this.sendTokenRequest(info)
	return err
}

//GetTicket
func (this *CToken) GetTicket(timeoutMS int64) (token string, err error) {

	if this.m_ticketJson.Ticket == "" {
		//处理 ticket //https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
		ticketUrl := GetTicketUrl + this.m_tokenJson.AccessToken
		re, err := http.NewRequest(http.MethodGet, ticketUrl, nil)
		es, err := http.DefaultClient.Do(re)
		defer es.Body.Close()
		if err != nil {
			this.m_isVaildTicket = false
			return "", err
		}
		body2, err := ioutil.ReadAll(es.Body)
		err = json.Unmarshal(body2, &this.m_ticketJson)
		this.m_isVaildTicket = true
	}

	return this.m_ticketJson.Ticket, nil
}

func (this *CToken) sendTokenRequest(info *common.CUserInfo) error {

	accessKey := "local:wx:token"
	accessKeyTime := "local:wx:time"
	//先从cache中获取
	tokenStr, _ := cache.RedisServer.Get(context.TODO(), accessKey)
	if tokenStr != "" {
		_ = json.Unmarshal([]byte(tokenStr), &this.m_tokenJson)
	} else {
		this.m_tokenJson.AccessToken = ""
		this.m_isVaild = false
	}

	if this.m_tokenJson.AccessToken != "" {
		this.m_isVaild = true
		return nil
	}

	if this.m_isVaild == false && cache.RedisServer.GetLock("wx_token") {
		url := GetTokenUrl
		req, err := http.NewRequest(http.MethodGet, url, nil)
		if err != nil {
			this.m_isVaild = false
			return err
		}
		values := req.URL.Query()
		values.Add(GrantType, ClientCredential)
		values.Add(AppId, info.AppId)
		values.Add(AppSecret, info.AppSecret)
		req.URL.RawQuery = values.Encode()
		res, err := http.DefaultClient.Do(req)
		if err != nil {
			this.m_isVaild = false
			return err
		}
		defer res.Body.Close()
		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			this.m_isVaild = false
			return err
		}
		err = json.Unmarshal(body, &this.m_tokenJson)
		if err != nil {
			this.m_isVaild = false
			return err
		}
		if this.m_tokenJson.ErrCode != common.ErrorCodeSuccess {
			this.m_isVaild = false
			return errors.New(this.m_tokenJson.ErrMsg)
		}

		day := time.Now().Format("2006-01-02 15:04:05")
		_, _ = cache.RedisServer.Set(context.TODO(), accessKey, string(body), 7200*time.Second)
		_, _ = cache.RedisServer.Set(context.TODO(), accessKeyTime, day, 8640000*time.Second)
	} else {
		return errors.New("失败：lock err")
	}

	if this.m_tokenJson.AccessToken == "" {
		this.m_isVaild = false
		return errors.New("失败：" + this.m_tokenJson.ErrMsg)
	}

	this.m_isVaild = true

	//处理 ticket //https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
	ticketUrl := GetTicketUrl + this.m_tokenJson.AccessToken
	re, err := http.NewRequest(http.MethodGet, ticketUrl, nil)
	es, err := http.DefaultClient.Do(re)
	defer es.Body.Close()
	if err != nil {
		this.m_isVaildTicket = false
		return err
	}
	body2, err := ioutil.ReadAll(es.Body)
	err = json.Unmarshal(body2, &this.m_ticketJson)
	this.m_isVaildTicket = true
	return nil
}

func New(info *common.CUserInfo) common.IToken {
	t := CToken{}
	t.init(info)
	return &t
}
